﻿using System;
using SoftwareConsulting.BI11.NumericalMethods.Regression;
using SoftwareConsulting.BI11.NumericalMethods.Interfaces;

namespace SoftwareConsulting.BI11.NumericalMethods.Statistics
{
    /// <summary>
    /// Класс нормального распределения (распределения Гаусса)
    /// </summary>
    public sealed class NormalDistribution : ProbabilityDensityFunction
    {        
        private double _muDistributionAverage;//среднее распределения
        private double _sigmaDistributionStandardDeviation;//стандартное отклонение рапределения        
        private static double _baseNorm = Math.Sqrt(2 * Math.PI);//константа для вычисления нормы        
        private static PolynomialFunction _errorFunctionSeries;//ряд для вычисления функции ошибки                
        private const double _errorFunctionConstant = 0.2316419;//константа для вычисления аргумента ряда функции ошибки

        /// <summary>
        /// Статический конструктор класса
        /// </summary>
        static NormalDistribution()
        {
            double[] coeffs = { 0.31938153, -0.356563782, 1.781477937, -1.821255978, 1.330274429 };
            _errorFunctionSeries = new PolynomialFunction(coeffs);
        }

        /// <summary>
        /// Конструктор класса по умолчанию
        /// </summary>
        public NormalDistribution() :
            this(0, 1)
        {
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="muDistributionAverage">среднее распределения</param>
        /// <param name="sigmaDistributionStandardDeviation">стандартное отклонение распределения</param>
        public NormalDistribution(double muDistributionAverage, double sigmaDistributionStandardDeviation)
        {
            if (sigmaDistributionStandardDeviation <= 0)
                throw new ArgumentOutOfRangeException("sigmaDistributionStandardDeviation");
            _muDistributionAverage = muDistributionAverage;
            _sigmaDistributionStandardDeviation = sigmaDistributionStandardDeviation;
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="histogram">Гистограмма, из которой вытягиваются параметры по наилучшим предположениям</param>
        public NormalDistribution(IHistogram histogram) : 
            this(histogram.Average, histogram.StandardDeviation)
        {
        }

        /// <summary>
        /// Возвращает/устанавливает среднее распределения
        /// </summary>
        public override double Average
        {
            get 
            { 
                return _muDistributionAverage; 
            }
            set 
            {
                _muDistributionAverage = value; 
            }
        }

        /// <summary>
        /// Возвращает значение функции плотности распределения
        /// </summary>
        /// <param name="x"></param>
        /// <returns>вероятность, нахождения случайной переменной меньше или равной, чем X</returns>
        public override double GetDistributionValue(double x)
        {
            return GetErrorFunctionValue((x - _muDistributionAverage) / _sigmaDistributionStandardDeviation);
        }

        /// <summary>
        /// Возвращает значение функции ошибки для указанного аргумента
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public static double GetErrorFunctionValue(double x)
        {
            if (x == 0)
                return 0.5;
            if (x > 0)
                return 1 - GetErrorFunctionValue(-x);
            double t = 1 / (1 - _errorFunctionConstant * x);
            return t * _errorFunctionSeries.GetValue(t) * GetNormalValue(x);
        }

        /// <summary>
        /// Возвращает эксцесс распределения
        /// </summary>
        public override double Kurtosis
        {
            get 
            { 
                return 0; 
            }
        }

        /// <summary>
        /// Возвращает название распределения
        /// </summary>
        public override string Name
        {
            get 
            { 
                return "Normal distribution"; 
            }
        }
        
        /// <summary>
        /// Возвращает значение функции плотности распределения (0,1)
        /// </summary>
        /// <param name="x">аргумент, для которого вычисляется вероятность</param>
        /// <returns></returns>
        public static double GetNormalValue(double x)
        {
            return Math.Exp(-0.5 * x * x) / _baseNorm;
        }

        /// <summary>
        /// Возвращает/устанавливает параметры распределения
        /// </summary>
        public override double[] Parameters
        {
            get 
            { 
                return new double[] { _muDistributionAverage, _sigmaDistributionStandardDeviation }; 
            }
            set
            {
                this.Average = value[0];
                this.StandardDeviation = value[1];
            }
        }

        /// <summary>
        /// Возвращает случайное вещественное число в соответствие с распределением
        /// </summary>
        /// <returns></returns>
        public override double GetRandomValue()
        {
            return base.RandomGenerator.NextGaussian() * _sigmaDistributionStandardDeviation + _muDistributionAverage;
        }

        /// <summary>
        /// Возвращает коэффициент уклона распределения
        /// </summary>
        public override double Skewness
        {
            get 
            {
                return 0; 
            }
        }

        /// <summary>
        /// Возвращает/устанавливает стандартное отклонение распределения
        /// </summary>
        public override double StandardDeviation
        {
            get 
            { 
                return _sigmaDistributionStandardDeviation; 
            }
            set 
            { 
                _sigmaDistributionStandardDeviation = value; 
            }
        }

        /// <summary>
        /// Возвращает как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format( "{0} ({1:0.00000},{2:0.00000})", Name, _muDistributionAverage, _sigmaDistributionStandardDeviation );
        }

        /// <summary>
        /// Вычисляет значение, основываясь на функции плотности
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public override double GetValue(double x)
        {
            return GetNormalValue((x - _muDistributionAverage) / _sigmaDistributionStandardDeviation) / _sigmaDistributionStandardDeviation;
        }

        /// <summary>
        /// Возвращает значение распределения и градиент распределения с учетом параметров
        /// </summary>
        /// <param name="x"></param>
        /// <returns>0й элемент - значение распределения, 1й и последующие - градиент распределения</returns>
        public override double[] GetValueAndGradient(double x)
        {
            double[] result = new double[3];
            double y = (x - _muDistributionAverage) / _sigmaDistributionStandardDeviation;
            result[0] = GetNormalValue(y) / _sigmaDistributionStandardDeviation;
            result[1] = result[0] * y / _sigmaDistributionStandardDeviation;
            result[2] = result[0] * (y * y - 1) / _sigmaDistributionStandardDeviation;
            return result;
        }
    }
}
